home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1998 March
/
Macworld (1998-03) (Disk 1).dmg
/
Shareware World
/
Utilities
/
Text Processing
/
Alpha
/
Tcl
/
Menus
/
filesetsMenu.tcl
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
NeXTSTEP
RISC OS/Acorn
UTF-8
Wrap
Text File
|
1997-12-20
|
46.1 KB
|
1,625 lines
|
[
TEXT/ALFA
]
## -*-Tcl-*-
# ###################################################################
# Vince's Additions - an extension package for Alpha
#
# FILE: "filesetsMenu.tcl"
# created: 20/7/96 {6:22:25 pm}
# last update: 20/12/97 {6:57:54 pm}
# Author: Vince Darley
# E-mail: <darley@fas.harvard.edu>
# mail: Division of Applied Sciences, Harvard University
# Oxford Street, Cambridge MA 02138, USA
# www: <http://www.fas.harvard.edu/~darley/>
#
#==============================================================================
# Alpha calls two fileset-related routines, 'getCurrFileSet', and
# 'getFileSetNames'. Alpha will also attempt to set the variable 'currFileSet'
# on occasion, but this isn't critical.
#==============================================================================
#
# modified by rev reason
# -------- --- --- -----------
# 24/3/96 VMD 1.0 update of Pete's original to allow mode-specific filesets
# 27/3/96 VMD 1.1 added hierarchial filesets, and checks for unique menus
# 13/6/96 VMD 1.2 memory efficiency improvements with 'fileSets' array
# 10/3/97 VMD 1.3 added 'procedural' fsets, including 'Open Windows'
# 6/4/97 VMD 1.31 various fixes incorporated - thanks!
# 11/7/97 VMD 1.4 added cache for the fileset menu, improved wc proc.
# 15/7/97 VMD 1.41 better handling of out-of-date filesets, and dir opening
# 15/7/97 VMD 1.42 placed cache in separate file.
# 21/7/97 VMD 1.43 added glob patterns to ignore for directory filesets
# 22/7/97 VMD 1.5 more sophisticated menu caching. No more long rebuilds!
# 10/9/97 VMD 1.6 simplified some stuff for new Alpha-Tcl
# 7/12/97 VMD 1.6.1 makes use of winNumDirty flag
# ###################################################################
##
##
# These procedures are now more robust and general-purpose. Basic new
# features are:
#
# * user configurable menu
# * unique-menu names are ensured, so there can be no clashes
# * new fileset types ('tex' and 'fromHierarchy')
# * new utility functions ('stuff', 'wordCount',...)
# * filesets need not appear in the menu; in fact they can be
# anywhere you like
#
# Known Bugs:
#
# You cannot have a hierarchial fileset which contains more than
# one folder with the same name as the fileset, including the
# base folder. This is very hard to fix, and the easy workaround
# is just to rename the fileset in some minor way.
##
alpha::menu filesetMenu 1.6.1 "•131" in_menu {
} uninstall {this-file} help {[editMark "$HOME:Help:Alpha Manual" "File Sets" -r]}
proc filesetMenu {} {}
# Build some filesets on the fly.
set gfileSets(Help) "$HOME:Help:*"
set gfileSets(System) [list "$HOME:Tcl:SystemCode:*.tcl" 2]
set gfileSets(Menus) "$HOME:Tcl:Menus:*.tcl"
set gfileSets(Modes) [list "$HOME:Tcl:Modes:*.tcl" 2]
set "gfileSets(Open Windows)" procFilesetOpenWindows
set "gfileSets(Top Window Folder)" procFilesetDirTopWin
# Declare their types
set gfileSetsType(Help) "fromDirectory"
set gfileSetsType(System) "fromHierarchy"
set gfileSetsType(Modes) "fromHierarchy"
set gfileSetsType(Menus) "fromDirectory"
set "gfileSetsType(Open Windows)" "procedural"
set "gfileSetsType(Top Window Folder)" "procedural"
# Procs for procedural filesets
proc procFilesetOpenWindows {} { return [winNames -f] }
proc procFilesetDirTopWin {} {
return [glob -t TEXT -nocomplain "[file dirname [win::Current]]:*"]
}
if {![file exists "$HOME:Tcl:Packages"]} { mkdir "$HOME:Tcl:Packages" }
set gfileSets(Packages) [list "$HOME:Tcl:Packages:*.tcl" 2]
set gfileSetsType(Packages) "fromHierarchy"
# Default curr fileset is the first one.
newPref var currFileSet "SystemCode" global changeFileSet gfileSets array
#################################################
# #
# Section 1: Fileset variables and flags. #
# #
#################################################
# Any of these can be over-ridden by the stored #
# definitions in defs.tcl, arrdefs.tcl #
#################################################
##
# We don't show the 'help' fileset, since it's under the MacOS
# AppleGuide menu. Also we could perhaps yank tex-filesets away
# into their own menu, in which case the tex-system could add to
# this variable as it went along.
##
lunion filesetsNotInMenu "Help" "Open Windows" "Top Window Folder"
##
# A type is a means of generating a fileset given its
# description in the variable 'gfileSets(name)':
##
lunion fileSetsTypes "list" "glob" "fromHierarchy" "procedural"
##
# A menu type is a means of prompting the user and
# characterising the interface to a type, even
# though the actual storage may be very simple
# (a list in most cases).
##
set fileSetsTypesThing(fromDirectory) "glob"
set fileSetsTypesThing(fromHierarchy) "fromHierarchy"
set fileSetsTypesThing(think) "list"
set fileSetsTypesThing(codewarrior) "list"
set fileSetsTypesThing(ftp) "list"
set fileSetsTypesThing(fromOpenWindows) "list"
set fileSetsTypesThing(procedural) "procedural"
##
# To add a new fileset type, you need to define the following:
# set fileSetsTypesThing(myType) "list"
# proc myTypeCreateFileset {} {}
# proc myTypeFilesetUpdate {name} {}
#
# For more complex types (e.g. the tex-type), define as follows:
# set fileSetsTypesThing(myType) "myType"
# proc myTypeCreateFileset {} {}
# proc myTypeFilesetSelected { fset menu item } {}
# proc myTypeFilesetUpdate { name } {}
# proc myTypeListFilesInFileset { name } {}
# proc myTypeMakeFileSetSubMenu { name } {}
#
# These procedures will all be called automatically under the
# correct circumstances. The purposes of these are as follows:
#
# 'create' -- query the user for name etc. and create
# 'update' -- given the information in 'gfileSets', recalculate
# the member files.
# 'selected' -- a member was selected in a menu.
# 'list' -- given info in all except 'fileSets', return list
# of files to be stored in that variable.
# 'submenu' -- generate the sub-menu
#
# Your code may wish to call 'isWindowInFileset ?win? ?type?' to
# check if a given (current by default) window is in a fileset of
# a given type.
##
##
# -------------------------------------------------------------------------
#
# "filesetSortOrder" --
#
# The structure of this variable dictates how the fileset
# menu is structured:
#
# '{pattern p}'
# lists all filesets which match 'p'
# '-'
# adds a separator line
# '{list of types}'
# lists all filesets of those types.
# '{submenu name sub-order-list}'
# adds a submenu with name 'name' and recursively
# adds filesets to that submenu as given by the
# sub-order.
#
# Leading, trailing and double separators are automatically
# removed.
#
# -------------------------------------------------------------------------
##
ensureset filesetSortOrder { {pattern *System} {pattern Packages} \
{pattern Menus} {pattern Modes} {pattern Preferences} \
- {tex} - {pattern *.cc} {submenu Headers {pattern *.h}} \
- {fromDirectory think codewarrior ftp \
fromOpenWindows fromHierarchy} * }
set "filesetUtils(browseFileset…)" [list * browseFileset]
set "filesetUtils(renameFileset…)" [list * renameFileset]
set "filesetUtils(openEntireFileset…)" [list * openEntireFileset]
set "filesetUtils(filesetToAlpha…)" [list * filesetToAlpha]
set "filesetUtils(closeEntireFileset…)" [list * closeEntireFileset]
set "filesetUtils(replaceInFileset…)" [list * replaceInFileset]
set "filesetUtils(stuffFileset…)" [list * stuffFileset]
set "filesetUtils(wordCount)" [list * wordCountFileset]
set "filesetUtils(wordCountFast)" [list * wordCountFilesetFast]
set "filesetUtils(openFilesetFolder…)" [list * openFilesetFolder]
##
# The meaning of these flags is as follows:
# sortFilesetItems --
# a type can have the option of being unsorted (e.g. tex-filesets)
# indentFilesetItems --
# visual formatting may be of relevance to some types
# sortFilesetsByType --
# use the variable 'filesetSortOrder' to determine the
# visual structure of the fileset menu
# autoAdjustFileset --
# when a file is selected from the menu, do we try and
# keep 'currFileSet' accurate?
# includeNonTextFiles --
# filesets may include non-text files. Alpha will tell the
# finder to open these if they are selected.
##
newPref flag sortFilesetItems 0 "fileset"
newPref flag indentFilesetItems 0 "fileset"
newPref flag sortFilesetsByType 0 "fileset" rebuildSomeFilesetMenu
newPref flag autoAdjustFileset 0 "fileset"
newPref flag includeNonTextFiles 0 "fileset" rebuildSomeFilesetMenu
# To add a new fileset type, all we have to do is this:
# set fileSetsTypesThing(tex) "tex"
# lappend fileSetsTypes "tex"
# If you create new types just add lines like that
#===========================================================================
# The support routines.
#===========================================================================
# Called from Alpha to get list of files for current file set.
proc getCurrFileSet {} {
global currFileSet
return [getFileSet $currFileSet]
}
# Called from Alpha to get names. The first name returned is taken to
# be the current fileset.
proc getFileSetNames {} {
global gfileSets currFileSet gDirScan
set perm [list $currFileSet]
set temp {}
set ind [lsearch [array names gfileSets] $currFileSet]
if {$ind < 0} {set ind 0}
foreach n [lsort -ignore [array names gfileSets]] {
if {[info exists gDirScan($n)]} {
lappend temp $n
} else {
lappend perm $n
}
}
if {$temp != {}} {
return [concat $perm - $temp]
} else {
return $perm
}
}
#================================================================================
# Edit a file from a fileset via list dialogs (no mousing around).
#================================================================================
proc editFile {} {
global currFileSet modifiedVars gfileSetsType
set fset [pickFileset "" {Fileset?} "list" [list {*recent*}]]
set currFileSet $fset
lappend modifiedVars currFileSet
if {$fset == {*recent*}} {return [editRecentFile]}
set ff [getFilesInSet $fset]
foreach f $ff {
lappend disp [file tail $f]
}
foreach res [listpick -l -p {File?} [lsort -ignore $disp]] {
set ind [lsearch $ff \*:$res]
if {$gfileSetsType($fset) == "ftp"} {
ftpFilesetOpen $fset [lindex $ff $ind]
} else {
catch {generalOpenFileitem [lindex $ff $ind]}
}
}
}
# We only return TEXT files, since we don't want Alpha
# manipulating the data fork of non-text files.
proc getFileSet {fset} {
global filesetmodeVars
if $filesetmodeVars(includeNonTextFiles) {
set fnames ""
foreach f [getFilesInSet $fset] {
if [file isfile $f] {
getFileInfo $f a
if {$a(type) == "TEXT"} {
lappend fnames $f
}
}
}
return $fnames
} else {
return [getFilesInSet $fset]
}
}
proc browseFileset {{fset ""}} {
global tileLeft tileTop tileWidth errorHeight
set fset [pickFileset $fset {Fileset?}]
foreach f [getFilesInSet $fset] {
append text "\t[file tail $f]\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t∞$f\r"
}
new -n "* FileSet '$fset' Browser *" -g $tileLeft $tileTop 200 $errorHeight -m Brws
insertText "(<cr> to go to file)\r-----\r$text\r"
select [nextLineStart [nextLineStart 0]] [nextLineStart [nextLineStart [nextLineStart 0]]]
winReadOnly
message ""
}
############################################
# #
# Section 2: Basic fileset procedures #
# #
############################################
proc newFileset {} {
global currFileSet gfileSetsType fileSetsTypesThing modifiedArrayElements
set type [dialog::optionMenu "New fileset type?" [lsort -ignore [array names fileSetsTypesThing]] "fromDirectory"]
set name [eval ${type}CreateFileset]
if ![string length $name] return
lappend modifiedArrayElements [list $name gfileSetsType]
set gfileSetsType($name) $type
set currFileSet $name
filesetsJustChanged $type $name
return $currFileSet
}
##
# -------------------------------------------------------------------------
#
# "filesetsJustChanged" --
#
# If we've added, deleted, modified a fileset, we call this procedure.
# In most cases we must rebuild everything (due to limitations in Alpha),
# but for 'procedural' filesets, we can just do the utilities menu.
# -------------------------------------------------------------------------
##
proc filesetsJustChanged {type name} {
if {$type == "procedural"} {
global filesetsNotInMenu modifiedVars
if {[lsearch $filesetsNotInMenu $name] == -1} {
lappend filesetsNotInMenu $name
lappend modifiedVars filesetsNotInMenu
}
rebuildFilesetUtilsMenu
} else {
rebuildAllFilesets 1
}
}
proc deleteFileset { {fset ""} {yes 0} } {
global fileSets gfileSets currFileSet fileSetsExtra gfileSetsType
global filesetMenu subMenuFilesetInfo subMenuInfo filesetsNotInMenu
global modifiedVars modifiedArrayElements
set fset [pickFileset $fset "Delete which Fileset?"]
if {$currFileSet == $fset} {catch {set currFileSet System}}
if {$yes || [dialog::yesno "Delete fileset \"$fset\"?"]} {
catch {unset "fileSetsExtra($fset)"}
catch {unset "gfileSetsType($fset)"}
catch {unset "fileSets($fset)"}
catch {unset "gfileSets($fset)"}
lappend modifiedArrayElements \
[list $fset gfileSetsType] [list $fset fileSetsExtra] \
[list $fset gfileSets]
set err [catch {removeFilesetFromMenu $fset}]
if {[set l [lsearch $filesetsNotInMenu $fset]] != -1} {
set filesetsNotInMenu [lreplace $filesetsNotInMenu $l $l]
lappend modifiedVars filesetsNotInMenu
deleteMenuItem -m choose $fset
deleteMenuItem -m hideFileset $fset
return
}
if $err {
# it's on a submenu or somewhere else so we just have
# to do the lot!
if !$yes { rebuildAllFilesets 1 }
} else {
deleteMenuItem -m choose $fset
deleteMenuItem -m hideFileset $fset
}
}
}
proc removeFilesetFromMenu {fset} {
global subMenuFilesetInfo subMenuInfo
# find its menu:
if [info exists subMenuFilesetInfo($fset)] {
foreach m $subMenuFilesetInfo($fset) {
# remove info about it's name
if [info exists subMenuInfo($m)] {
unset subMenuInfo($m)
cache::add filesetMenuCache "eval" [list unset subMenuInfo($m)]
}
}
set base [lindex $subMenuFilesetInfo($fset) 0]
unset subMenuFilesetInfo($fset)
cache::add filesetMenuCache "eval" [list unset subMenuFilesetInfo($fset)]
cache::snippetRemove $fset
# this will fail if it's on a submenu or if it isn't a menu at all
deleteMenuItem -m $filesetMenu $base
cache::add filesetMenuCache "eval" [list deleteMenuItem -m $filesetMenu $base]
} else {
# I think I do nothing
}
}
##
# -------------------------------------------------------------------------
#
# "pickFileset" --
#
# Ask the user for a/several filesets. If 'fset' is set, we just
# return that (this avoids 'if {$fset != ""} { set fset [pick...] }
# constructs everywhere). A prompt can be given, and a dialog type
# (either a listpick, a pop-up menu, or a listpick with multiple
# selection), and extra items can be added to the list if desired.
# -------------------------------------------------------------------------
##
proc pickFileset { fset {prompt Fileset?} {type "list"} {extras {}} } {
global gfileSets currFileSet
if { $fset != "" } { return $fset }
switch $type {
"popup" {
set fset [eval [list prompt $prompt \
$currFileSet "FileSet:"] [lsort -ignore [array names gfileSets]]]
if ![info exists gfileSets($fset)] { error "No such fileset" }
return $fset
}
"list" {
return [listpick -p $prompt -L $currFileSet \
[lsort -ignore [concat $extras [array names gfileSets]]]]
}
"multilist" {
return [listpick -p $prompt -l -L $currFileSet \
[lsort -ignore [concat $extras [array names gfileSets]]]]
}
}
}
proc renameFileset {} {
global fileSets gfileSets currFileSet fileSetsExtra gfileSetsType
global fileSetsTypesThing modifiedArrayElements
set fset [pickFileset "" {Fileset to rename?}]
set name [getline "Rename to:" $fset]
if {![string length $name] || $name == $fset} return
set gfileSets($name) $gfileSets($fset)
set gfileSetsType($name) $gfileSetsType($fset)
catch {set fileSets($name) $fileSets($fset)}
catch {set fileSetsExtra($name) $fileSetsExtra($fset)}
deleteFileset $fset 1
lappend modifiedArrayElements [list $name gfileSets]
lappend modifiedArrayElements [list $name gfileSetsType]
lappend modifiedArrayElements [list $name fileSetsExtra]
filesetsJustChanged $gfileSetsType($name) $name
set currFileSet $name
}
proc updateCurrentFileset {} {
global currFileSet
updateAFileset $currFileSet
}
proc updateAFileset { {fset ""} } {
set fset [pickFileset $fset]
global gfileSetsType fileSets subMenuFilesetInfo subMenuInfo
set type $gfileSetsType($fset)
catch {eval [list "${type}FilesetUpdate" $fset] }
set m [makeFileSetAndMenu $fset 1]
# we could rebuild the menu with this: but we don't
cache::add filesetMenuCache "eval" $m
if {[info exists subMenuFilesetInfo($fset)]} {
# if the fileset already has a base menu, use that:
foreach n $subMenuFilesetInfo($fset) {
cache::add filesetMenuCache "variable" subMenuInfo($n)
}
cache::add filesetMenuCache "variable" subMenuFilesetInfo($n)
}
if [info exists fileSets($fset)] {
cache::add filesetMenuCache "variable" fileSets($fset)
}
eval $m
callFilesetUpdateProcedures $fset
message "Done"
}
proc callFilesetUpdateProcedures { {fset ""} } {
global filesetUpdateProcs gfileSetsType
if { $fset == "" } {
set types [array names filesetUpdateProcs]
} else {
set types $gfileSetsType($fset)
}
foreach l $types {
if [info exists filesetUpdateProcs($l)] {
foreach proc $filesetUpdateProcs($l) {
eval $proc
}
}
}
}
##################################################
# #
# Section 3: Creation of basic fileset types #
# #
##################################################
proc proceduralCreateFileset {} {
global gfileSets gfileSetsType filesetsNotInMenu modifiedArrayElements
set name [getline "Name for this fileset…"]
if {![string length $name]} return
set gfileSetsType($name) "procedural"
set p procFileset[join $name ""]
set gfileSets($name) $p
addUserLine "\# procedure to list files in fileset '$name' on the fly"
addUserLine "proc $p \{\} \{"
addUserLine "\t"
addUserLine "\}"
lappend modifiedArrayElements [list $name gfileSets]
lappend modifiedArrayElements [list $name gfileSetsType]
if {[dialog::yesno "I've added a template for the procedure to your 'prefs.tcl'. Do you want to edit it now?"]} {
global::editPrefsFile
goto [maxPos]
beep
message "Make sure you 'load' the new procedure."
}
lappend filesetsNotInMenu $name
return $name
}
proc fromDirectoryCreateFileset {} {
global gfileSets gfileSetsType fileSetsExtra
set name [getFilesetDirectoryAndPattern]
if ![string length $name] return
set filePatIgnore [getline "List of file patterns to ignore:" ""]
if {$filePatIgnore != ""} {
set fileSetsExtra($name) $filePatIgnore
}
set gfileSetsType($name) "fromDirectory"
if {[dialog::yesno "Save new fileset?"]} {
global modifiedArrayElements
lappend modifiedArrayElements [list $name gfileSets]
lappend modifiedArrayElements [list $name gfileSetsType]
if [info exists fileSetsExtra($name)] {
lappend modifiedArrayElements [list $name fileSetsExtra]
}
}
return $name
}
proc getFilesetDirectoryAndPattern {} {
global gfileSets fileSetsExtra
set name [getline "New fileset name:" ""]
if {![string length $name]} return
set dir [string trim [get_directory -p "New fileset dir:"] ":"]
if {![string length $dir]} return
set filePat [getline "File pattern:" "*"]
if {![string length $filePat]} return
set gfileSets($name) "$dir:$filePat"
return $name
}
proc fromDirectoryFilesetUpdate {name} {
# done on the fly so no need to update
#global fileSets gfileSets
#set fileSets($name) [glob -nocomplain -t TEXT "$gfileSets($name)"]
}
proc fromHierarchyCreateFileset {} {
global gfileSets gfileSetsType
set name [getFilesetDirectoryAndPattern]
if ![string length $name] return
set gfileSetsType($name) "fromHierarchy"
set depth [listpick -p "Depth of hierarchy?" -L 3 {1 2 3 4 5 6 7}]
if { $depth == "" } {set depth 3}
set gfileSets($name) [list $gfileSets($name) $depth]
if {[dialog::yesno "Save new fileset?"]} {
global modifiedArrayElements
lappend modifiedArrayElements [list $name gfileSets]
lappend modifiedArrayElements [list $name gfileSetsType]
}
return $name
}
proc fromHierarchyFilesetUpdate {name} {
global fileSets gfileSets
set fileSets($name) [fromHierarchyListFilesInFileSet $name]
}
proc fromHierarchyMakeFileSetAndMenu {name andMenu} {
global filesetTemp fileSets gfileSets
set dir [file dirname [lindex $gfileSets($name) 0]]
set patt [file tail [lindex $gfileSets($name) 0]]
set depth [lindex $gfileSets($name) 1]
# we make the menu as a string, but can bin it if we like
set menu [buildSubMenu [list $dir] $name filesetProc filesetTemp $patt $depth $name]
# we need to construct the list of items
set fileSets($name) {}
if [info exists filesetTemp] {
foreach n [array names filesetTemp] {
lappend fileSets($name) $filesetTemp($n)
}
unset filesetTemp
}
return $menu
}
proc fromHierarchyFilesetSelected {fset menu item} {
global gfileSets
set dir [file dirname [lindex $gfileSets($fset) 0]]
set ff [getFilesInSet $fset]
if { $fset == $menu } {
# it's top level
if {[set match [lsearch $ff ${dir}:$item]] >= 0} {
autoUpdateFileset $fset
generalOpenFileitem [lindex $ff $match]
return
}
}
# the following two are slightly cumbersome, but give us the best
# chance of finding the correct file given any ambiguity (which can
# certainly arise if file and directory names clash excessively).
if {[set match [lsearch $ff ${dir}:${menu}:$item]] >= 0} {
autoUpdateFileset $fset
generalOpenFileitem [lindex $ff $match]
return
}
if {[set match [lsearch $ff ${dir}:*:${menu}:$item]] >= 0} {
autoUpdateFileset $fset
generalOpenFileitem [lindex $ff $match]
return
}
error "Weird! Couldn't find it."
}
proc codewarriorCreateTagFile {} { return [alphaCreateTagFile] }
proc thinkCreateTagFile {} { return [alphaCreateTagFile] }
proc codewarriorCreateFileset {} { return [createWarriorFileset] }
proc thinkCreateFileset {} { return [createThinkFileset] }
proc fromOpenWindowsCreateFileset {} {
global gfileSets modifiedArrayElements
set name [prompt "Create fileset containing current windows under what name?" "OpenWins"]
set gfileSets($name) [winNames -f]
lappend modifiedArrayElements [list $name gfileSets]
return $name
}
##################################
# #
# Section 4: Menu Procedures #
# #
##################################
##
# Global procedures to deal with the fact that Alpha can only have one
# menu with each given name. This is only a problem in dealing with
# user-defined menus such as fileset menus, tex-package menus, ...
##
##
# -------------------------------------------------------------------------
#
# "makeFilesetSubMenu" --
#
# If desired this is the only procedure you need use --- it returns
# a menu creation string, taking account of the unique name requirement
# and will make sure your procedure 'proc' is called with the real
# menu name!
# -------------------------------------------------------------------------
##
proc makeFilesetSubMenu {fset name proc args} {
if { [string length $proc] > 1 } {
return [concat {menu -m -n} [list [registerFilesetMenuName $fset $name $proc]] -p subMenuProc $args]
} else {
return [concat {menu -m -n} [list [registerFilesetMenuName $fset $name]] $args]
}
}
##
# -------------------------------------------------------------------------
#
# "registerFilesetMenuName" --
#
# Call to ensure unique fileset submenu names. We just add spaces
# as appropriate and keep track of everything for you! Filesets
# which have multiple menus _must_ register the main menu first.
# -------------------------------------------------------------------------
##
proc registerFilesetMenuName {fset name {proc ""}} {
global subMenuInfo subMenuFilesetInfo
if { $fset == $name && [info exists subMenuFilesetInfo($fset)] } {
# if the fileset already has a base menu, use that:
foreach n $subMenuFilesetInfo($fset) {
if { [string trimright $n] == $fset } {
set base $n
}
unset subMenuInfo($n)
}
unset subMenuFilesetInfo($fset)
}
set original $name
if [info exists base] {
set name $base
} else {
# I add at least one space to _all_ hierarchical submenus now.
# This is so I won't clash with any current or future modes
# which should never normally add spaces themselves.
append name " "
while { [info exists subMenuInfo($name)] } {
append name " "
}
}
set subMenuInfo($name) [list "$fset" "$original" "$proc"]
# build list of a fileset's menus
lappend subMenuFilesetInfo($fset) "$name"
return $name
}
proc realMenuName {name} {
global subMenuInfo
return [lindex $subMenuInfo($name) 1]
}
##
# -------------------------------------------------------------------------
#
# "subMenuProc" --
#
# This procedure is implicitly used to deal with ensuring unique
# sub-menu names. It calls the procedure you asked for, with
# the name of the menu you think you're using.
# -------------------------------------------------------------------------
##
proc subMenuProc {menu item} {
global subMenuInfo
set l $subMenuInfo($menu)
set realProc [lindex $l 2]
if {[info commands $realProc] == ""} {catch "$realProc"}
# try to call the proc with three arguments (fileset is 1st)
if {[llength [info args $realProc]] == 2} {
$realProc [lindex $l 1] "$item"
} else {
$realProc [lindex $l 0] [lindex $l 1] "$item"
}
}
proc filesetMenuProc {menu item} {
switch $item {
"Edit File" {
editFile
return
}
"Help" {
global HOME
editMark "$HOME:Help:Alpha Manual" "File Sets" -r
return
}
}
}
##
# -------------------------------------------------------------------------
#
# "filesetProc" --
#
# Must be called by 'subMenuProc'
# -------------------------------------------------------------------------
##
proc filesetProc {fset menu item} {
global gfileSetsType
if {$fset != ""} {set m $fset} else { set m $menu}
switch $gfileSetsType($m) {
"fromDirectory" -
"think" -
"codewarrior" -
"fromOpenWindows" {
if [catch {filesetBasicOpen $m $item}] {
if {[dialog::yesno "That file wasn't found. That fileset is probably out of date; do you want to rebuild it?"]} {
updateAFileset $fset
}
}
}
"ftp" { ftpFilesetOpen $m $item }
"default" {
# try a type-specific method first
set proc $gfileSetsType($m)FilesetSelected
if {[info commands $proc] == ""} {
# auto-load it
catch $proc
}
if {[info commands $proc] != ""} {
if {[llength [info args $proc]] == 2} {
if ![catch {eval [list $proc $menu $item]}] {return}
} else {
if ![catch {eval [list $proc $fset $menu $item]}] {return}
}
} else {
# if that failed then just hope it's an ordinary list
if ![catch {filesetBasicOpen $m $item}] {return}
}
if {[dialog::yesno "That file wasn't found. That fileset is probably out of date; do you want to rebuild it?"]} {
updateAFileset $fset
}
}
}
}
proc filesetBasicOpen { menu item } {
if {[set match [lsearch [getFilesInSet $menu] *:$item]] >= 0} {
autoUpdateFileset $menu
generalOpenFileitem [lindex [getFilesInSet $menu] $match]
return
}
error "file not found"
}
##
# -------------------------------------------------------------------------
#
# "generalOpenFileitem" --
#
# Works around an alpha bug with aliases.
# -------------------------------------------------------------------------
##
proc generalOpenFileitem {file} {
if [file isfile $file] {
file::openAny $file
} else {
# is it an alias?
if {[file type $file] == "unknown"} {
getFileInfo $file a
# is it a folder?
if {$a(type) != "fdrp"} {
file::openAny $file
}
}
findFile "${file}:"
}
}
proc registerUpdateProcedure { type proc } {
global filesetUpdateProcs
lappend filesetUpdateProcs($type) [list $proc]
}
proc filesetUtilsProc { menu item } {
global filesetUtils gfileSetsType currFileSet
if [info exists filesetUtils($item)] {
# it's a utility
set utilDesc $filesetUtils($item)
set allowedTypes [lindex $utilDesc 0]
if [string match $allowedTypes $gfileSetsType($currFileSet)] {
return [eval [lindex $utilDesc 1]]
} else {
beep
message "That utility can't be applied to the current file-set."
return
}
} else {
$item
}
}
proc getFilesInSet {fset} {
global gfileSets fileSetsTypesThing gfileSetsType
switch $fileSetsTypesThing($gfileSetsType($fset)) {
"list" {
return $gfileSets($fset)
}
"glob" {
global filesetmodeVars fileSetsExtra
if $filesetmodeVars(includeNonTextFiles) {
set l [glob -nocomplain "$gfileSets($fset)"]
if [info exists fileSetsExtra($fset)] {
foreach pat $fileSetsExtra($fset) {
foreach f [glob -nocomplain [file dirname "$gfileSets($fset)"]:$pat] {
set i [lsearch $l $f]
set l [lreplace $l $i $i]
}
}
}
return $l
} else {
set l [glob -nocomplain -t TEXT "$gfileSets($fset)"]
if [info exists fileSetsExtra($fset)] {
foreach pat $fileSetsExtra($fset) {
foreach f [glob -nocomplain -t TEXT [file dirname "$gfileSets($fset)"]:$pat] {
set i [lsearch $l $f]
set l [lreplace $l $i $i]
}
}
}
return $l
}
}
"procedural" {
return [$gfileSets($fset)]
}
"default" {
global fileSets
if ![info exists fileSets($fset)] {
# This means the menu was cached, but this info wasn't.
# We calculate the set, and menu, and cache them
# (since they're at the end of the file, they over-ride
# what's there.
# we rebuild the menu too
eval [makeFileSetAndMenu $fset 1]
cache::add filesetMenuCache "variable" fileSets($fset)
}
return $fileSets($fset)
}
}
}
proc makeFileSetAndMenu {name andMenu {use_cache 0}} {
if $use_cache {
set m [cache::snippetRead $name]
if {$m != ""} {return $m}
}
global gfileSetsType fileSetsTypesThing
message "Building ${name}..."
set type $gfileSetsType($name)
switch $fileSetsTypesThing($type) {
"list" -
"glob" {
if $andMenu {
set menu {}
foreach m [getFilesInSet $name] {
lappend menu "[file tail $m]&"
}
set m [makeFilesetSubMenu $name $name filesetProc [lsort -i $menu]]
} else {
return
}
}
"procedural" {
return
}
"default" {
set m [${type}MakeFileSetAndMenu $name $andMenu]
}
}
cache::snippetWrite $name $m
return $m
}
proc filesetsSorted { order usedvar {use_cache 0}} {
upvar $usedvar used
global filesetmodeVars gfileSets gfileSetsType
set sets {}
foreach item $order {
switch -- [lindex $item 0] {
"-" {
# add divider
lappend sets "(-"
continue
}
"*" {
# add all the rest
set subset {}
foreach s [array names gfileSets] {
if ![lcontains used $s] {
lappend subset $s
lappend used $s
}
}
foreach f [lsort $subset] {
lappend sets [makeFileSetAndMenu $f 1 $use_cache]
}
}
"pattern" {
# find all which match a given pattern
set patt [lindex $item 1]
set subset {}
foreach s [array names gfileSets] {
if ![lcontains used $s] {
if [string match $patt $s] {
lappend subset $s
lappend used $s
}
}
}
foreach f [lsort $subset] {
lappend sets [makeFileSetAndMenu $f 1 $use_cache]
}
}
"submenu" {
# add a submenu with name following and sub-order
set name [lindex $item 1]
set suborder [lrange $item 2 end]
# we make kind of a pretend fileset here.
set subsets [filesetsSorted $suborder used]
if { $subsets != "" } {
lappend sets [makeFilesetSubMenu $name $name filesetProc $subsets]
}
}
"default" {
set subset {}
foreach s [array names gfileSets] {
if {[lcontains item $gfileSetsType($s)] && ![lcontains used $s]} {
lappend subset $s
lappend used $s
}
}
foreach f [lsort $subset] {
lappend sets [makeFileSetAndMenu $f 1 $use_cache]
}
}
}
}
# remove multiple and leading, trailing '-' in case there were gaps
regsub -all {\(-( \(-)+} $sets {(-} sets
while { [lindex $sets 0] == "(-" } { set sets [lrange $sets 1 end] }
set l [expr [llength $sets] -1]
if { [lindex $sets $l] == "(-" } { set sets [lrange $sets 0 [incr l -1]] }
return $sets
}
##
# -------------------------------------------------------------------------
#
# "rebuildFilesetMenu" --
#
# Reads the fileset menu from the cache if it exists. This speeds up
# start-up by quite a bit.
# -------------------------------------------------------------------------
##
proc rebuildFilesetMenu {} {
message "Building filesets..."
if [cache::exists filesetMenuCache] {
global subMenuFilesetInfo subMenuInfo fileSets
cache::read filesetMenuCache
rebuildFilesetUtilsMenu
callFilesetUpdateProcedures
} else {
rebuildAllFilesets 1
}
}
##
# -------------------------------------------------------------------------
#
# "zapAndBuildFilesets" --
#
# This does a complete rebuild of all information. The problem is that
# the names of menus may actually change (spaces added/deleted). This
# is not a problem for the fileset menu, but is a problem for any
# filesets which have been added to other menus, since they won't know
# that they need to be rebuilt.
# -------------------------------------------------------------------------
##
proc zapAndBuildFilesets {} {
global subMenuInfo subMenuFilesetInfo
unset subMenuInfo
unset subMenuFilesetInfo
rebuildAllFilesets
}
proc rebuildAllFilesets { {use_cache 0} } {
global gfileSets filesetMenu filesetSortOrder
global filesetmodeVars filesetsNotInMenu fileSets
message "Rebuilding filesets menu…"
if $filesetmodeVars(sortFilesetsByType) {
# just make file-sets for those we don't want in the menu
if {!$use_cache} {
foreach f $filesetsNotInMenu {
makeFileSetAndMenu $f 0
}
}
set used $filesetsNotInMenu
set sets [filesetsSorted $filesetSortOrder used $use_cache]
} else {
foreach f [lsort [array names gfileSets]] {
set doMenu [expr ![lcontains filesetsNotInMenu $f]]
set menu [makeFileSetAndMenu $f $doMenu $use_cache]
if { $doMenu && $menu != "" } {
lappend sets $menu
}
}
}
regsub -all {[-][nm]} $sets "" names
set names [map cadr $names]
set names [map "string trimright" $names]
# cache the fileset menu
set m [list menu -m -n $filesetMenu -p filesetMenuProc \
[concat {{/'Edit File…} {menu -n Utilities {}}} "Help" \
"(-" $sets]]
cache::create filesetMenuCache
cache::add filesetMenuCache "eval" $m [list insertMenu $filesetMenu]
global subMenuFilesetInfo subMenuInfo
cache::add filesetMenuCache "variable" subMenuFilesetInfo subMenuInfo fileSets
eval $m
rebuildFilesetUtilsMenu
callFilesetUpdateProcedures
message ""
}
##
# -------------------------------------------------------------------------
#
# "rebuildSomeFilesetMenu" --
#
# If given '*' rebuild the entire menu, else rebuild only those types
# given. This is generally useful to avoid excessive rebuilding when
# flags are adjusted
# -------------------------------------------------------------------------
##
proc rebuildSomeFilesetMenu {args} {
rebuildAllFilesets
}
proc rebuildFilesetUtilsMenu {} {
global gfileSets filesetUtils
menu -n "Utilities" -p filesetUtilsProc [concat \
"newFileset…" \
"deleteFileset…" \
"<S<EupdateAFileset…" \
"<SupdateCurrentFileset" \
"<S<EzapAndBuildFilesets" \
"<SrebuildAllFilesets" \
[list [menu::makeFlagMenu choose list currFileSet]] \
[list [list menu -n hideFileset -m -p hideShowFileset [lsort [array names gfileSets]]]] \
[list [menu::makeFlagMenu filesetFlags array filesetmodeVars]] \
"(-" \
"/T<I<OfindTag" \
"createTagFile" \
"(-" \
[lsort [array names filesetUtils]] \
]
filesetUtilsMarksTicks
}
proc rebuildSimpleFilesetMenus {} {
global gfileSets fileSetsTypesThing
eval [menu::makeFlagMenu choose list currFileSet]
menu -n hideFileset -m -p hideShowFileset [lsort [array names gfileSets]]
filesetUtilsMarksTicks
}
proc hideShowFileset {menu item} {
global filesetsNotInMenu filesetMenu
if [lcontains filesetsNotInMenu $item] {
global gfileSetsType
if {$gfileSetsType($item) == "procedural"} {
alertnote "Sorry, procedural filesets are completely dynamic and cannot appear in menus."
return
}
set idx [lsearch $filesetsNotInMenu $item]
set filesetsNotInMenu [lreplace $filesetsNotInMenu $idx $idx]
markMenuItem -m hideFileset $item off
# would be better if we could just insert it
rebuildAllFilesets 1
} else {
lappend filesetsNotInMenu $item
markMenuItem -m hideFileset $item on
if [catch {removeFilesetFromMenu $item}] {
rebuildAllFilesets 1
}
}
global modifiedVars
lappend modifiedVars filesetsNotInMenu
}
proc filesetUtilsMarksTicks {} {
global filesetsNotInMenu
foreach name $filesetsNotInMenu {
markMenuItem -m hideFileset $name on
}
}
# Called in response to user changing filesets from the fileset menu.
proc changeFileSet {item} {
global currFileSet tagFile
# Bring in the tags file for this fileset
set fname [tagFileName]
if {[file exists $fname]} {
if {[dialog::yesno "Use tag file from folder \"$dir\" ?"]} {
set tagFile $fname
}
}
}
proc autoUpdateFileset { name } {
global currFileSet filesetmodeVars modifiedVars
if $filesetmodeVars(autoAdjustFileset) {
set currFileSet $name
lunion modifiedVars currFileSet
}
}
#############################################
# #
# Section 5: General Utility procedures #
# #
#############################################
proc isWindowInFileset { {win "" } {type ""} } {
if {$win == ""} { set win [win::Current] }
global currFileSet gfileSets gfileSetsType
if { $type == "" } {
set okSets [array names gfileSets]
} else {
set okSets {}
foreach s [array names gfileSets] {
if { $gfileSetsType($s) == $type } {
lappend okSets $s
}
}
}
if [array exists gfileSets] {
if {[lsearch -exact $okSets $currFileSet] != -1 } {
# check current fileset
if {[lsearch -exact [getFilesInSet $currFileSet] $win] != -1 } {
# we're set, it's in this fileset
return $currFileSet
}
}
# check other fileset
foreach fset $okSets {
if {[lsearch -exact [getFilesInSet $fset] $win] != -1 } {
# we're set, it's in this project
return $fset
}
}
}
return ""
}
##
# -------------------------------------------------------------------------
#
# "iterateFileset" --
#
# Utility procedure to iterate over all files in a project,
# calling some predefined function '$fn' for each member of
# project '$proj'. The results of such a call are passed to
# '$resfn' if given. Finally "done" is passed to 'resfn'.
#
# -------------------------------------------------------------------------
##
proc iterateFileset { proj fn { resfn \# } } {
global gfileSets gfileSetsType
eval $resfn "first"
set check [expr ![catch {$gfileSetsType($proj)IterateCheck check}]]
foreach ff [getFileSet $proj] {
if { $check && [$gfileSetsType($proj)IterateCheck $proj $ff] } {
continue
}
set res [eval $fn \{$ff\}]
eval $resfn \{$res\}
}
if $check {
catch {$gfileSetsType($proj)IterateCheck done}
}
eval $resfn "done"
}
########################
# #
# Section 6: Tags #
# #
########################
if ![string length [info commands alphaFindTag]] {
rename findTag alphaFindTag
rename createTagFile alphaCreateTagFile
}
proc tagFileName {} {
global gfileSets currFileSet
return [file dirname [car $gfileSets($currFileSet)]]:[join ${currFileSet}]TAGS
}
proc findTag {} {
global gfileSetsType currFileSet
# try a type-specific method first
if [catch {$gfileSetsType($currFileSet)FindTag}] {
alphaFindTag
}
}
proc createTagFile {} {
global gfileSetsType currFileSet tagFile modifiedVars
set tagFile [tagFileName]
lappend modifiedVars tagFile
# try a type-specific method first
if [catch {$gfileSetsType($currFileSet)CreateTagFile}] {
alphaCreateTagFile
}
}
############################
# #
# Section 7: Utils #
# #
############################
proc dirtyFileset { fset } {
foreach f [getFilesInSet $fset] {
if { ![catch {getWinInfo -w $f arr}] && $arr(dirty)} { return 1 }
}
return 0
}
proc saveEntireFileset { fset } {
foreach f [getFilesInSet $fset] {
if { ![catch {getWinInfo -w $f arr}] && $arr(dirty)} {
bringToFront $f
save
}
}
}
proc closeEntireFileset { {fset ""} } {
set fset [pickFileset $fset "Close which fileset?" "popup"]
foreach f [getFilesInSet $fset] {
if ![catch {getWinInfo -w $f arr}] {
bringToFront $f
killWindow
}
}
}
proc fileToAlpha {f} {
if {[file isfile $f] && ([getFileType $f] == "TEXT") && ([getFileSig $f] != "ALFA")} {
message "Converting $f"
setFileInfo $f creator ALFA
}
}
proc filesetToAlpha {} {
set fset [pickFileset "" {Convert all files from which fileset?} "popup"]
iterateFileset $fset fileToAlpha
}
##
# -------------------------------------------------------------------------
#
# "replaceInFileset" --
#
# Quotes things correctly so searches work, and adds a check on
# whether there are any windows.
# -------------------------------------------------------------------------
##
proc replaceInFileset {} {
global gfileSets win::NumDirty
set how [dialog::optionMenu "Search type:" \
[list "Textual replace" "Case-independent textual replace" \
"Regexp replace" "Case-independent regexp replace"] "" 1]
set from [prompt "Search string:" [searchString]]
searchString $from
if {$how < 2} {set from [quote::Regfind $from]}
set to [prompt "Replace string:" [replaceString]]
replaceString $to
if {$how < 2} {set to [quote::Regsub $to]}
if [catch {regsub $from "$from" $to dummy} err] {
alertnote "Regexp compilation problems: $err"
return
}
set fsets [pickFileset "" "Which filesets?" "multilist"]
if {${win::NumDirty}} {
if {[buttonAlert "Save all windows?" "Yes" "Cancel"] != "Yes"} return
saveAll
}
set cid [scancontext create]
set changes 0
if {$how & 1} {
set case "-nocase"
} else {
set case "--"
}
scanmatch $case $cid $from {set matches($f) 1 ;incr changes}
foreach fset $fsets {
foreach f [getFileSet $fset] {
if {![catch {set fid [open $f]}]} {
message "Looking at '[file tail $f]'"
scanfile $cid $fid
close $fid
}
}
}
scancontext delete $cid
foreach f [array names matches] {
message "Modifying ${f}…"
set cid [open $f "r"]
if {[regsub -all $case $from [read $cid] $to out]} {
set ocid [open $f "w+"]
puts -nonewline $ocid $out
close $ocid
}
close $cid
}
revertTheseFiles [array names matches]
message "Replaced $changes instances"
}
proc openEntireFileset {} {
set fset [pickFileset "" "Open which fileset?" "popup"]
# we use our iterator in case there's something special to do
iterateFileset $fset "edit -c -w"
}
proc openFilesetFolder {} {
global gfileSets
set fset [pickFileset "" "Open which fileset's folder?" "popup"]
if {[llength $gfileSets($fset)] == 1 && [file isdirectory [set dir [file dirname $gfileSets($fset)]]]} {
openFolder $dir
} else {
alertnote "Fileset not connected to a folder."
}
}
proc stuffFileset {} {
global gfileSetsType gfileSets
set fset [pickFileset "" "Which fileset shall I stuff?" "popup"]
if [string length $fset] {
if { $gfileSetsType($fset) == "fromDirectory" && \
[dialog::yesno "Stuff entire directory?"]} {
app::launchFore DStf
sendOpenEvent reply 'DStf' "[file dirname $gfileSets($fset)]:"
} else {
app::launchFore DStf
eval sendOpenEvents 'DStf' [getFilesInSet $fset]
}
sendQuitEvent 'DStf'
}
}
proc filesetRememberOpenClose { file } {
global fileset_openorclosed
set fileset_openorclosed [list "$file" [lsearch -exact [winNames -f] $file]]
}
proc filesetRevertOpenClose { file } {
global fileset_openorclosed
if { [lindex $fileset_openorclosed 0] == "$file" } {
if { [lindex $fileset_openorclosed 1] < 0 } {
killWindow
}
}
catch {unset fileset_openorclosed}
}
proc wordCountFileset {} {
global currFileSet
iterateFileset $currFileSet wordCountProc filesetUtilWordCount
}
proc wordCountFilesetFast {} {
global currFileSet
iterateFileset $currFileSet wc filesetUtilWordCount
}
proc filesetUtilWordCount {count} {
global fs_ccount fs_wcount fs_lcount
switch $count {
"first" {
set fs_ccount 0
set fs_wcount 0
set fs_lcount 0
}
"done" {
alertnote "There were $fs_ccount lines, $fs_wcount words and $fs_lcount chars"
unset fs_ccount fs_wcount fs_lcount
}
default {
incr fs_ccount [lindex $count 2]
incr fs_wcount [lindex $count 1]
incr fs_lcount [lindex $count 0]
}
}
}
##
# -------------------------------------------------------------------------
#
# "wordCountProc" --
#
# Completely new proc which does the same as the old one
# without opening lots of windows.
# *Very* memory comsuming for large files, though.
# But I think the old one was equally memeory consuming.
#
# Ok, this is not exactly a bug fix. It's a IMHO better option.
#
# -------------------------------------------------------------------------
##
proc wordCountProc {file} {
message "Counting [file tail $file]…"
set fid [open $file r]
set filecont [read $fid]
close $fid
if {[regexp {\n\r} $filecont]} {
set newln "\n\r"
} elseif {[regexp {\n} $filecont]} {
set newln "\n"
} else {
set newln "\r"
}
set lines [expr [regsub -all $newln $filecont " " filecont] + 1]
set chars [string length $filecont]
regsub -all {[!=;.,\(\#\=\):\{\"\}]} $filecont " " filecont
set words [llength $filecont]
return "$chars $words $lines"
}
############################################
# Section 2: Basic fileset procedures #
############################################
proc findNewFileset {} {
return [newFileset]
}
proc findNewDirectory {} {
global gfileSets currFileSet gfileSetsType gDirScan
set dir [string trim [get_directory -p "Scan which folder?"] ":"]
if {![string length $dir]} return
set filePat {*}
set name [file tail $dir]
set gfileSets($name) "$dir:$filePat"
set gDirScan($name) 1
set gfileSetsType($name) "fromDirectory"
set currFileSet $name
updateCurrentFileset
return $name
}
# Should be last so all filesets make it in.
rebuildFilesetMenu